import glfw
import glm
from OpenGL.GL import *
from OpenGL.GLU import *
import numpy as np
import ctypes
import time
import random
from PIL import Image

vertexShaderScorce = '''
    #version 330 core
    layout (location = 0) in vec3 aPos;
    layout (location = 1) in vec2 aTexCoord;

    out vec2 TexCoord;

    uniform mat4 model;
    uniform mat4 view;
    uniform mat4 projection;

    void main()
    {
        gl_Position = projection * view * model * vec4(aPos, 1.0);
        TexCoord = vec2(aTexCoord.x, 1.0 - aTexCoord.y);
    }
'''


fragmentShaderSource = '''
    #version 330 core
    out vec4 FragColor;

    in vec2 TexCoord;
    
    // texture sampler
    uniform sampler2D texture1;
    uniform sampler2D texture2;
    uniform float mixValue;

    void main()
    {    
        FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), mixValue);
    }
    
'''


def framebuffer_size_callback(window, width, height):
    glViewport(0, 0, width, height)

def processInput(window):
    if glfw.get_key(window, glfw.KEY_ESCAPE) == glfw.PRESS:
        glfw.set_window_should_close(window, True)

def OurShader(vertexShaderScorce, fragmentShaderSource):
    vertexShader = glCreateShader(GL_VERTEX_SHADER)
    glShaderSource(vertexShader, vertexShaderScorce)
    glCompileShader(vertexShader)
    if not glGetShaderiv(vertexShader, GL_COMPILE_STATUS):
        print("Failed to compile vertex shader")
        raise Exception("Failed to compile vertex shader")

    fragmentShader = glCreateShader(GL_FRAGMENT_SHADER)
    glShaderSource(fragmentShader, fragmentShaderSource)
    glCompileShader(fragmentShader)
    if not glGetShaderiv(fragmentShader, GL_COMPILE_STATUS):
        print("Failed to compile fragment shader")
        raise Exception("Failed to compile fragment shader")

    shaderProgram = glCreateProgram()
    glAttachShader(shaderProgram, vertexShader)
    glAttachShader(shaderProgram, fragmentShader)
    glLinkProgram(shaderProgram)
    if not glGetProgramiv(shaderProgram, GL_LINK_STATUS):
        glGetShaderInfoLog(shaderProgram)
        print("Failed to link shader program")
        raise Exception("Failed to link shader program")

    glDeleteShader(vertexShader)
    glDeleteShader(fragmentShader)

    return shaderProgram



if __name__ == "__main__":
    if not glfw.init():
        print("Failed to initialize GLFW")
        raise Exception("Failed to initialize GLFW")
    glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
    glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3)
    glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)

    window = glfw.create_window(800, 600, "Learn OpenGL", None, None)
    if not window:
        print("Failed to create GLFW window")
        glfw.terminate()
        raise Exception("Failed to create GLFW window")

    glfw.make_context_current(window)
    glfw.set_framebuffer_size_callback(window, framebuffer_size_callback)

    glEnable(GL_DEPTH_TEST)

    shaderProgram = OurShader(vertexShaderScorce, fragmentShaderSource)

    vertices = np.array([
        # positions          texture coords
        -0.5, -0.5, -0.5,  0.0, 0.0,
         0.5, -0.5, -0.5,  1.0, 0.0,
         0.5,  0.5, -0.5,  1.0, 1.0,
         0.5,  0.5, -0.5,  1.0, 1.0,
         -0.5, 0.5, -0.5,  0.0, 1.0,
         -0.5, -0.5, -0.5,  0.0, 0.0,

         -0.5, -0.5,  0.5,  0.0, 0.0,
          0.5, -0.5,  0.5,  1.0, 0.0,
          0.5,  0.5,  0.5,  1.0, 1.0,
          0.5,  0.5,  0.5,  1.0, 1.0,
          -0.5, 0.5,  0.5,  0.0, 1.0,
          -0.5, -0.5,  0.5,  0.0, 0.0,

         -0.5,  0.5,  0.5,  1.0, 0.0,
         -0.5,  0.5, -0.5,  1.0, 1.0,
         -0.5, -0.5, -0.5,  0.0, 1.0,
         -0.5, -0.5, -0.5,  0.0, 1.0,
         -0.5, -0.5,  0.5,  0.0, 0.0,
         -0.5,  0.5,  0.5,  1.0, 0.0,

          0.5,  0.5,  0.5,  1.0, 0.0,
          0.5,  0.5, -0.5,  1.0, 1.0,
          0.5, -0.5, -0.5,  0.0, 1.0,
          0.5, -0.5, -0.5,  0.0, 1.0,
          0.5, -0.5,  0.5,  0.0, 0.0,
          0.5,  0.5,  0.5,  1.0, 0.0,

         -0.5, -0.5, -0.5,  0.0, 1.0,
          0.5, -0.5, -0.5,  1.0, 1.0,
          0.5, -0.5,  0.5,  1.0, 0.0,
          0.5, -0.5,  0.5,  1.0, 0.0,
          -0.5, -0.5,  0.5,  0.0, 0.0,
         -0.5, -0.5, -0.5,  0.0, 1.0,

         -0.5,  0.5, -0.5,  0.0, 1.0,
          0.5,  0.5, -0.5,  1.0, 1.0,
          0.5,  0.5,  0.5,  1.0, 0.0,
          0.5,  0.5,  0.5,  1.0, 0.0,
          -0.5, 0.5,  0.5,  0.0, 0.0,
         -0.5,  0.5, -0.5,  0.0, 1.0
    ], dtype=np.float32)

    cubePositions = [
        glm.vec3(0.0, 0.0, 0.0),
        glm.vec3(2.0, 5.0, -15.0),
        glm.vec3(-1.5, -2.2, -2.5),
        glm.vec3(-3.8, -2.0, -12.3),
        glm.vec3(2.4, -0.4, -3.5),
        glm.vec3(-1.7, 3.0, -7.5),
        glm.vec3(1.3, -2.0, -2.5),
        glm.vec3(1.5, 2.0, -2.5),
        glm.vec3(1.5, 0.2, -1.5),
        glm.vec3(-1.3, 1.0, -1.5)
    ]

    VAO = glGenVertexArrays(1)
    VBO = glGenBuffers(1)
    EBO = glGenBuffers(1)

    glBindVertexArray(VAO)

    glBindBuffer(GL_ARRAY_BUFFER, VBO)
    glBufferData(GL_ARRAY_BUFFER, vertices.nbytes, vertices, GL_STATIC_DRAW)

    # glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO)
    # glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.nbytes, indices, GL_STATIC_DRAW)

    # position attribute
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * vertices.itemsize, ctypes.c_void_p(0))
    glEnableVertexAttribArray(0)
    # texture coord attribute
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * vertices.itemsize, ctypes.c_void_p(3 * vertices.itemsize))
    glEnableVertexAttribArray(1)

    # load and create a texture
    texture1 = glGenTextures(1)
    glBindTexture(GL_TEXTURE_2D, texture1)
    # set the texture wrapping parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
    # set texture filtering parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
    # load image, create texture and generate mipmaps
    image = Image.open(r"images\container.jpg")
    # image = image.transpose(Image.FLIP_TOP_BOTTOM)
    img_data = image.convert("RGB").tobytes('raw', 'RGB', 0, -1)
    if not img_data:
        print("Failed to load texture")
        raise Exception("Failed to load texture")
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.width, image.height, 0, GL_RGB, GL_UNSIGNED_BYTE, img_data)
    glGenerateMipmap(GL_TEXTURE_2D)
    del img_data

    texture2 = glGenTextures(1)
    glBindTexture(GL_TEXTURE_2D, texture2)
    # set the texture wrapping parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
    # set texture filtering parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
    # load image, create texture and generate mipmaps
    image = Image.open(r"images\awesomeface.png")
    # image = image.transpose(Image.FLIP_TOP_BOTTOM)
    # image = image.transpose(Image.FLIP_LEFT_RIGHT)
    img_data = image.convert("RGB").tobytes('raw', 'RGB', 0, -1)
    if not img_data:
        print("Failed to load texture")
        raise Exception("Failed to load texture")
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.width, image.height, 0, GL_RGB, GL_UNSIGNED_BYTE, img_data)
    glGenerateMipmap(GL_TEXTURE_2D)
    del img_data

    glUseProgram(shaderProgram)
    glUniform1i(glGetUniformLocation(shaderProgram, "texture1"), 0)
    glUniform1i(glGetUniformLocation(shaderProgram, "texture2"), 1)

    mixValue = 0.5
    while not glfw.window_should_close(window):
        processInput(window)

        glClearColor(0.2, 0.3, 0.3, 1.0)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        
        
        if glfw.get_key(window, glfw.KEY_UP) == glfw.PRESS:
            mixValue += 0.01
        elif glfw.get_key(window, glfw.KEY_DOWN) == glfw.PRESS:
            mixValue -= 0.01
        mixValue = np.clip(mixValue, 0.0, 1.0)
        glUniform1f(glGetUniformLocation(shaderProgram, "mixValue"), mixValue)

        glActiveTexture(GL_TEXTURE0)
        glBindTexture(GL_TEXTURE_2D, texture1)
        glActiveTexture(GL_TEXTURE1)
        glBindTexture(GL_TEXTURE_2D, texture2)

        glUseProgram(shaderProgram)

        ############## 坐标系统 ##############
        # V_clip = M_proj * M_view * V_model * V_object
        model = glm.mat4(1.0)
        view = glm.mat4(1.0)
        projection = glm.mat4(1.0)
        model = glm.rotate(model, glfw.get_time(), glm.vec3(0.5, 1.0, 0.0))
        view = glm.translate(view, glm.vec3(0.0, 0.0, -3.0))
        projection = glm.perspective(glm.radians(45.0), 800.0 / 600.0, 0.1, 100.0)
        modelLoc = glGetUniformLocation(shaderProgram, "model")
        viewLoc = glGetUniformLocation(shaderProgram, "view")
        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm.value_ptr(model))
        glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm.value_ptr(view))
        projectionLoc = glGetUniformLocation(shaderProgram, "projection")
        glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm.value_ptr(projection))
        ############## 坐标系统 ##############

        glBindVertexArray(VAO)
        # for i in range(10):
        #     model = glm.mat4(1.0)
        #     model = glm.translate(model, cubePositions[i])
        #     angle = 20.0 * i
        #     model = glm.rotate(model, glm.radians(angle), glm.vec3(1.0, 0.3, 0.5))
        #     glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm.value_ptr(model))
        #     glDrawArrays(GL_TRIANGLES, 0, 36)
        glDrawArrays(GL_TRIANGLES, 0, 36)

        glfw.swap_buffers(window)
        glfw.poll_events()
    
    glDeleteVertexArrays(1, [VAO])
    glDeleteBuffers(1, [VBO])
    glDeleteBuffers(1, [EBO])
    glfw.terminate()